package org.egrit;

import java.awt.Point;

/**
 * Base class for grid iterators that impose additional conditions on cell inclusion
 * (e.g. cell centre lies within a polygon).
 * During use, all cells within the bounds (passed to the iterator upon construction)
 * are tested with the {@linkplain #testInclusion(java.awt.Point)} method which must
 * be overridden by sub-classes.
 *
 * @author michael
 */
public abstract class AbstractConditionalGridBoundsIterator extends GridBoundsIterator {
    
    protected final Point nextIncludedPos;
    private boolean initialized;
    
    /**
     * Creates a new conditional iterator.
     * 
     * @param poly the masking polygon
     * @param bounds grid bounds
     */
    public AbstractConditionalGridBoundsIterator(IGridBounds bounds) {
        super(bounds);
        this.nextIncludedPos = new Point(minPos.x - 1, minPos.y);
        this.initialized = false;
    }

    @Override
    public boolean hasNext() {
        ensureInitialized();
        return super.hasNext() && (nextIncludedPos.x <= maxPos.x || nextIncludedPos.y <= maxPos.y);
    }

    @Override
    public boolean next() {
        ensureInitialized();
        if (hasNext()) {
            pos.setLocation(nextIncludedPos);
            advanceNextIncludedPos();
            return true;
        }
        return false;
    }

    @Override
    public Point getPos() {
        ensureInitialized();
        return super.getPos();
    }

    @Override
    public void reset() {
        ensureInitialized();
        if (!emptyBounds) {
            nextIncludedPos.setLocation(minPos.x - 1, minPos.y);
            if (advanceNextIncludedPos()) {
                pos.setLocation(nextIncludedPos);
                finished = false;
            
                // advance again to store next included position (if present)
                advanceNextIncludedPos();
            }
        }
    }

    /**
     * Tests a grid cell position for inclusion. All cells within the 
     * iterator bounds are passed to this method.
     * 
     * @param candidatePos the grid cell position to test
     * 
     * @return true if the position should be included by this iterator
     */
    protected abstract boolean testInclusion(Point candidatePos);

    private boolean advanceNextIncludedPos() {
        boolean advanced = true;
        do {
            if (nextIncludedPos.x < maxPos.x) {
                nextIncludedPos.x++;
            } else if (nextIncludedPos.y < maxPos.y) {
                nextIncludedPos.x = minPos.x;
                nextIncludedPos.y++;
            } else {
                advanced = false;
                break;
            }
        } while (!testInclusion(nextIncludedPos));
        
        
        if (!advanced) {
            // move next position past max position so that hasNext 
            // notices that we have finished
            nextIncludedPos.setLocation(maxPos.x + 1, maxPos.y + 1);
            
            // also set the finished flag (the super-class will only set it if
            // we are at the end of the grid bounds)
            finished = true;
        }
        
        return advanced;
    }

    private void ensureInitialized() {
        if (!initialized) {
            if (!emptyBounds && advanceNextIncludedPos()) {
                pos.setLocation(nextIncludedPos);

                // advance again to store next included position (if present)
                advanceNextIncludedPos();

            } else {
                // no cells inside the polygon
                pos.setLocation(0, 0);
            }

            initialized = true;
        }
    }
}
